use figment::{
    providers::{Env, Format, Serialized, Toml},
    value::{Dict, Map},
    Metadata, Profile,
};
use figment::{Figment, Provider};
use serde::{Deserialize, Serialize};
use std::net::{IpAddr, Ipv4Addr};
use ubyte::ByteUnit;
use uncased::Uncased;

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Limits {
    #[serde(with = "figment::util::vec_tuple_map")]
    limits: Vec<(Uncased<'static>, ByteUnit)>,
}

impl Default for Limits {
    fn default() -> Limits {
        Limits::new()
            .limit("form", Limits::FORM)
            .limit("data-form", Limits::DATA_FORM)
            .limit("file", Limits::FILE)
            .limit("string", Limits::STRING)
            .limit("bytes", Limits::BYTES)
            .limit("json", Limits::JSON)
            .limit("msgpack", Limits::MESSAGE_PACK)
    }
}

impl Limits {
    /// Default limit for value-based forms.
    pub const FORM: ByteUnit = ByteUnit::Kibibyte(32);

    /// Default limit for data-based forms.
    pub const DATA_FORM: ByteUnit = ByteUnit::Mebibyte(2);

    /// Default limit for temporary files.
    pub const FILE: ByteUnit = ByteUnit::Mebibyte(1);

    /// Default limit for strings.
    pub const STRING: ByteUnit = ByteUnit::Kibibyte(8);

    /// Default limit for bytes.
    pub const BYTES: ByteUnit = ByteUnit::Kibibyte(8);

    /// Default limit for JSON payloads.
    pub const JSON: ByteUnit = ByteUnit::Mebibyte(1);

    /// Default limit for MessagePack payloads.
    pub const MESSAGE_PACK: ByteUnit = ByteUnit::Mebibyte(1);

    #[inline]
    pub fn new() -> Self {
        Limits { limits: vec![] }
    }

    pub fn limit<S: Into<Uncased<'static>>>(mut self, name: S, limit: ByteUnit) -> Self {
        let name = name.into();
        match self.limits.binary_search_by(|(k, _)| k.cmp(&name)) {
            Ok(i) => self.limits[i].1 = limit,
            Err(i) => self.limits.insert(i, (name, limit)),
        }

        self
    }

    pub fn get<S: AsRef<str>>(&self, name: S) -> Option<ByteUnit> {
        let mut name = name.as_ref();
        let mut indices = name.rmatch_indices('/');
        loop {
            let exact_limit = self
                .limits
                .binary_search_by(|(k, _)| k.as_uncased_str().cmp(name.into()))
                .map(|i| self.limits[i].1);

            if let Ok(exact) = exact_limit {
                return Some(exact);
            }

            let (i, _) = indices.next()?;
            name = &name[..i];
        }
    }

    pub fn find<S: AsRef<str>, L: AsRef<[S]>>(&self, layers: L) -> Option<ByteUnit> {
        let layers = layers.as_ref();
        for j in (1..=layers.len()).rev() {
            let layers = &layers[..j];
            let opt = self
                .limits
                .binary_search_by(|(k, _)| {
                    let k_layers = k.as_str().split('/');
                    k_layers.cmp(layers.iter().map(|s| s.as_ref()))
                })
                .map(|i| self.limits[i].1);

            if let Ok(byte_unit) = opt {
                return Some(byte_unit);
            }
        }

        None
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
    #[serde(skip)]
    pub profile: Profile,
    ///监听地址
    pub address: IpAddr,
    ///监听端口
    pub port: u16,
    ///工作核心数
    pub workers: usize,

    ///流所承载的最大负载
    pub limits: Limits,
}

impl Default for Config {
    fn default() -> Config {
        #[cfg(debug_assertions)]
        {
            Config::debug_default()
        }
        #[cfg(not(debug_assertions))]
        {
            Config::release_default()
        }
    }
}

impl Config {
    /// The default debug profile: `debug`.
    pub const DEBUG_PROFILE: Profile = Profile::const_new("debug");

    /// The default release profile: `release`.
    pub const RELEASE_PROFILE: Profile = Profile::const_new("release");

    /// The default profile: "debug" on `debug`, "release" on `release`.
    #[cfg(debug_assertions)]
    pub const DEFAULT_PROFILE: Profile = Self::DEBUG_PROFILE;

    /// The default profile: "debug" on `debug`, "release" on `release`.
    #[cfg(not(debug_assertions))]
    pub const DEFAULT_PROFILE: Profile = Self::RELEASE_PROFILE;
}

/// Associated constants for stringy versions of configuration parameters.
impl Config {
    /// The stringy parameter name for setting/extracting [`Config::profile`].
    ///
    /// This isn't `pub` because setting it directly does nothing.
    const PROFILE: &'static str = "profile";

    /// The stringy parameter name for setting/extracting [`Config::address`].
    pub const ADDRESS: &'static str = "address";

    /// The stringy parameter name for setting/extracting [`Config::port`].
    pub const PORT: &'static str = "port";

    /// The stringy parameter name for setting/extracting [`Config::workers`].
    pub const WORKERS: &'static str = "workers";

    /// The stringy parameter name for setting/extracting [`Config::keep_alive`].
    pub const KEEP_ALIVE: &'static str = "keep_alive";

    /// The stringy parameter name for setting/extracting [`Config::limits`].
    pub const LIMITS: &'static str = "limits";
}

impl Config {
    pub fn debug_default() -> Config {
        Config {
            profile: Self::DEBUG_PROFILE,
            address: Ipv4Addr::new(127, 0, 0, 1).into(),
            port: 8000,
            workers: num_cpus::get(),
            limits: Limits::default(),
            // log_level: LogLevel::Normal,
        }
    }

    pub fn release_default() -> Config {
        Config {
            profile: Self::RELEASE_PROFILE,
            ..Config::debug_default()
        }
    }

    pub fn figment() -> Figment {
        Figment::from(Config::default())
            .merge(Toml::file(Env::var_or("RAPID_CONFIG", "Rapid.toml")).nested())
            .merge(Env::prefixed("RAPID_").ignore(&["PROFILE"]).global())
            .select(Profile::from_env_or("RAPID_PROFILE", Self::DEFAULT_PROFILE))
    }
}

impl Provider for Config {
    fn metadata(&self) -> Metadata {
        Metadata::named("Rapid Config")
    }

    fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
        let map = Serialized::defaults(self).data()?;

        Ok(map)
    }
    fn profile(&self) -> Option<figment::Profile> {
        Some(self.profile.clone())
    }
}
